/*
 * Sapphire
 *
 * Copyright (C) 2018 Florrie Haero
 * Copyright (C) 2018 Alyssa Rosenzweig
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 *
 */

/* The active socket itself */

let backendWS = null;

/* Delay connecting to the websocket to prevent race conditions */

const WS_PORT = 7070;

window.backendConnect = function(username, passwordHash) {
    const protocol = 'ws' + (window.location.protocol === 'https:' ? 's': '') + '://';
    const url = protocol + window.BASE_HOST + ':' + WS_PORT + '/ws';

    /* Whether we ever managed to connect */
    let once_connected = false;

    console.log(`Connecting to ${url}...`);

    backendWS = new WebSocket(url);

    backendWS.onopen = function() {
        console.log('open');
        once_connected = true;
        backendWS.send(JSON.stringify({username, passwordHash}));
    };

    backendWS.onclose = function() {
        console.log('closed');
        sapphireCallHandler('wsclosed', once_connected);
    };

    backendWS.onmessage = sapphireGotMessage;
};

/* Maintain set of buddy/chats IDs */

const backendIDType = {};

/* Handlers to be filled in by the frontend */

window.backendHooks = {
    message: null,
    batchMessage: null,
    newBuddy: null,
    newChat: null,
    newAccount: null,
    changeAvatar: null,
    joined: null,
    typing: null,
    topic: null,
    wsclosed: null
};

/* Serialize and send off an event to the server */

function sendEvent(data) {
    backendWS.send(JSON.stringify(data));
}

function sendEventWithTarget(data, id) {
    data[backendIDType[id]] = id;
    sendEvent(data);
}

/* Events exposed to the frontend */

window.backendMessage = (target, content) => {
    sendEventWithTarget({
        op: 'message', content
    }, target);
};

window.backendTyping = (target, state) => {
    sendEventWithTarget({
        op: 'typing', state
    }, target);
};

window.backendJoinChat = (id) => {
    sendEvent({
        op: 'joinChat', id
    });
};

window.backendMarkAsRead = (id) => {
    sendEvent({
        op: 'markAsRead', id
    });
};

window.backendRequestBuddy = (account, id, alias, invite) => {
    sendEvent({
        op: 'requestBuddy', account, id, alias, invite
    });
};

window.backendChangeAvatar = (account, base64) => {
    sendEvent({
        op: 'changeAvatar', account, base64
    });
};

/* Internal handlers */

function sapphireGotWorld(data) {
    /* A world packet contains the buddy list and the account list. Iterate
     * these separately and defer to the usual handler */

    for (const account of data.accounts) {
        window.backendHooks.newAccount(account);
    }

    /* Make sure accounts are enabled before replaying messages */

    /* Function to replay missed messages, in the right (reversed) order */
    const unack = function(obj) {
        if (obj.unacked) {
            const unacked = obj.unacked.reverse();

            for (const message of unacked) {
                window.backendHooks.batchMessage(message);
            }
        }

        window.backendHooks.flushBatched();
    };

    for (const buddy of data.buddies) {
        backendIDType[buddy.id] = 'buddy';
        window.backendHooks.newBuddy(buddy);
        unack(buddy);
    }

    for (const chat of data.chats) {
        backendIDType[chat.id] = 'chat';
        window.backendHooks.newChat(chat);
        unack(chat);
    }
}

const sapphireCallHandler = (op, ...args) => {
    /* Handlers for internal use */

    const internalHandlers = {
        world: sapphireGotWorld
    };

    /* Invoke a handler */

    const handler = window.backendHooks[op] || internalHandlers[op];

    if (handler) {
        handler(...args);
    } else {
        console.error('Missing handler for op: ' + op);
    }
};

const sapphireGotMessage = (event) => {
    /* On receiving a message, we need to decode it (from JSON) and then
     * process it accordingly */

    let obj;
    const str = event.data;

    try {
        obj = JSON.parse(str);
    } catch(e) {
        console.error('Parse error\n');
        console.error('> ' + str);
        console.error(e);
        return;
    }

    console.log(obj);

    /* Parsed, now invoke a handler to actually do something with it */

    sapphireCallHandler(obj.op, obj);
};
